/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/slab.h>

#include <alga/alga.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/edid.h>
#include <alga/amd/atombios/atb.h>
#include <alga/amd/atombios/dce.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/dce6/dce6_dev.h>

#include "dce6.h"

static int start_send(struct i2c_adapter *a, u16 addr, u8 read)
{
	u8 mode;
	struct dce6 *dce;
	struct dp *dp;

	mode = ATB_MODE_I2C_START;
	if (read)
		mode |= ATB_MODE_I2C_READ;
	else
		mode |= ATB_MODE_I2C_WRITE;

	dce = a->algo_data;
	dp = container_of(a, struct dp, i2c_adapter);
	return atb_dp_aux_i2c(dce->ddev.atb, dp->atb_aux_i2c_id, dp->hpd, addr,
								mode, 0, NULL);
}

static int stop_send(struct i2c_adapter *a, u16 addr, u8 read)
{
	u8 mode;
	struct dce6 *dce;
	struct dp *dp;

	mode = ATB_MODE_I2C_STOP;
	if (read)
		mode |= ATB_MODE_I2C_READ;
	else
		mode |= ATB_MODE_I2C_WRITE;

	dce = a->algo_data;
	dp = container_of(a, struct dp, i2c_adapter);
	return atb_dp_aux_i2c(dce->ddev.atb, dp->atb_aux_i2c_id, dp->hpd, addr,
								mode, 0, NULL);
}

static int byte_send(struct i2c_adapter *a, u16 addr, u8 byte)
{
	struct dce6 *dce;
	struct dp *dp;

	dce = a->algo_data;
	dp = container_of(a, struct dp, i2c_adapter);
	return atb_dp_aux_i2c(dce->ddev.atb, dp->atb_aux_i2c_id, dp->hpd, addr,
						ATB_MODE_I2C_WRITE, byte, NULL);
}

static int byte_recv(struct i2c_adapter *a, u16 addr, u8 *byte)
{
	struct dce6 *dce;
	struct dp *dp;

	dce = a->algo_data;
	dp = container_of(a, struct dp, i2c_adapter);
	return atb_dp_aux_i2c(dce->ddev.atb, dp->atb_aux_i2c_id, dp->hpd, addr,
						ATB_MODE_I2C_READ, 0, byte);
}

static int i2c_transac(struct i2c_adapter *a, struct i2c_msg *msgs, int num)
{
	int r;
	u8 read;
	u32 m;
	u32 b;

	r = 0;
	read = 0;
	for (m = 0; m < (u32)num; ++m) {
		u16 len = msgs[m].len;
		u8 *buf = msgs[m].buf;
		read = (msgs[m].flags & I2C_M_RD) != 0;

		r = start_send(a, msgs[m].addr, read); /* or restart */
		if (r < 0)
			break;

		if (read) {
			for (b = 0; b < len; ++b) {
				r = byte_recv(a, msgs[m].addr, &buf[b]);
				if (r < 0)
					break;
			}
		} else {
			for (b = 0; b < len; ++b) {
				r = byte_send(a, msgs[m].addr, buf[b]);
				if (r < 0)
					break;
			}
		}
		if (r < 0)
			break;
	}
	if (num > 0)
		r = stop_send(a, msgs[num - 1].addr, read);
	if (r >= 0)
		r = num;
	return r;
}

static u32 functionality(struct i2c_adapter *a)
{
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |
	       I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL |
	       I2C_FUNC_10BIT_ADDR;
}

static const struct i2c_algorithm dp_i2c_algo = {
	.master_xfer	= i2c_transac,
	.functionality	= functionality
};

long dp_i2c_adapter_init(struct dce6 *dce, u8 i)
{
	struct i2c_adapter *a;
	long r;

	a = &dce->dps[i].i2c_adapter;

	memset(a, 0, sizeof(*a));
	a->owner = THIS_MODULE;
	a->class = I2C_CLASS_DDC;
	a->algo = &dp_i2c_algo;
	a->algo_data = dce;
	a->retries = 3;
	a->dev.parent = dce->ddev.dev;
	snprintf(a->name, sizeof(a->name), "dp%u", i);
	r = i2c_add_adapter(a);
	if (r != 0)
		return -DCE6_ERR;
	return 0;
}

void dp_i2c_cleanup(struct dce6 *dce)
{
	u8 i;

	for (i = 0; i < dce->ddev.crtcs_n; ++i) {
		if ((dce->dps_used & BIT(i)) == 0)
			continue;

		i2c_del_adapter(&dce->dps[i].i2c_adapter);
	}
}
